package com.uxsino.simo.collector.connections;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.uxsino.simo.connections.exception.SimoConnectionException;

public class H3cCasConnection extends HTTPConnection/*HuaweiCloudConnection*/ {

    static Logger logger = LoggerFactory.getLogger(H3cCasConnection.class);

    @Override
    public Object execCmd(Object cmdPattern) throws SimoConnectionException {
        Map<String, String> cmdMap = JSONStringToHashMap((String) cmdPattern);
        String cmdStr = cmdMap.get("relativeURL");
        Map<String, Map<String, String>> cmdStrMap = new HashMap<String, Map<String, String>>();
        Map<String, List<Object>> paramMap = new HashMap<String, List<Object>>();
        Map<String, String> vdcURLMap = JSONStringToHashMap(cmdMap.get("vdcURL"));
        Map<String, String> paramsToResultMap = JSONStringToHashMap(cmdMap.get("paramsToResult"));
        for (String cmdPatternStr : vdcURLMap.keySet()) {
            String params = (String) vdcURLMap.get(cmdPatternStr);
            if (StringUtils.isNotBlank(params)) {
                String prefix = null;
                if (cmdPatternStr.split("->").length > 1) {
                    prefix = cmdPatternStr.split("->")[1];
                }
                cmdPatternStr = cmdPatternStr.split("->")[0];
                cmdStrMap = combineCmdWithParams(cmdPatternStr, paramMap, null);
                StringBuilder parameResult = new StringBuilder();
                cmdStrMap.keySet().forEach(cmd -> {
                    try {
                        String parameResultStr = (String) super.execCmd(cmd);
                        parameResultStr = removeTitle(parameResultStr);
                        parameResult.append(parameResultStr);
                    } catch (SimoConnectionException e) {
                        logger.error("cas 获取参数的query {} 执行错误：", cmd, e);
                    }
                });
                String[] paramArray = params.split(",");
                Map<String, String> elements = new HashMap<String, String>();
                for (int i = 0; i < paramArray.length; i++) {
                    String[] paramReplace = paramArray[i].split("->");
                    String replaceFrom = paramReplace[0];
                    String replaceTo = paramReplace[1];
                    elements.put(replaceTo, replaceFrom);
                }
                String parameResultStr = parameResult.toString();
                parameResultStr = "<list>" + parameResultStr;
                parameResultStr = parameResultStr + "</list>";
                paramMap.putAll(xml2ParamMap(parameResultStr, elements, prefix));
            }
        }
        logger.info("cas 指标查询query支持的所有变量：{}", JSON.toJSONString(paramMap));
        cmdStrMap = combineCmdWithParams(cmdStr, paramMap, paramsToResultMap);
        logger.info("cas 指标查询query集成变量后支持的所有url：{}", cmdStrMap.keySet());
        StringBuilder cmdResult = new StringBuilder();
        for (String cmd : cmdStrMap.keySet()) {
            try {
                String result = (String) super.execCmd(cmd);
                if (result.startsWith("<?")) {
                    result = paramsToResult(result, cmdStrMap.get(cmd));
                    cmdResult.append(result);
                }
            } catch (SimoConnectionException e) {
                logger.error("cas 采集指标数据的query {}执行错误：", cmd, e);
            }
        }
        String cmdResultStr = cmdResult.toString();
        cmdResultStr = "<list>" + cmdResultStr;
        cmdResultStr = cmdResultStr + "</list>";
        logger.info("cas 的query-->{} 采集的原始数据：{}", cmdStr, cmdResultStr);
        return cmdResultStr.toString();
    }

    private String removeTitle(String input) {
        input = input.replaceAll("<\\?.*\\?>", "");
        return input;
    }

    /**
     *  替换url中的占位符为变量值
     * @param cmdStr
     * @param paramMap
     * @param paramKeyMap
     * @return 替换后的所有url
     */
    private Map<String, Map<String, String>> combineCmdWithParams(String cmdStr, Map<String, List<Object>> paramMap,
        Map<String, String> paramToResultMap) {
        Map<String, Map<String, String>> cmdMap = new HashMap<String, Map<String, String>>();
        Map<Integer, Map<String, String>> cmdIndexElemValue = new HashMap<Integer, Map<String, String>>();

        if (!cmdStr.contains("{")) {
            cmdMap.put(cmdStr, new HashMap<String, String>());
            return cmdMap;
        }
        int count = 1;
        for (String paramKey : paramMap.keySet()) {
            List<Object> params = paramMap.get(paramKey);
            if (params != null) {
                count *= params.size();
            }
        }
        List<String> cmdList = new ArrayList<String>();
        for (int i = 0; i < count; i++) {
            cmdList.add(cmdStr);
        }
        int sameCount = count;
        for (String paramKey : paramMap.keySet()) {
            List<Object> params = paramMap.get(paramKey);
            if (params != null) {

                String elem = paramToResultMap == null ? null : paramToResultMap.get(paramKey);

                sameCount /= params.size();
                int replaceTimes = count / params.size();
                for (Object param : params) {
                    int times = 0;
                    for (String cmd : cmdList) {
                        int i = cmdList.indexOf(cmd);
                        if (cmd.contains("{" + paramKey + "}")) {
                            String cmdReplaceTo = cmd.replace("{" + paramKey + "}", (String) param);
                            List<String> sameCmd = cmdList.stream()
                                .filter(cmdReplaced -> cmdReplaced.equals(cmdReplaceTo)).collect(Collectors.toList());
                            if (sameCmd.size() >= sameCount && cmdList.contains(cmd)) {
                                continue;
                            }
                            cmdList.set(i, cmdReplaceTo);

                            if (StringUtils.isNotBlank(elem)) {
                                if (!cmdIndexElemValue.containsKey(i)) {
                                    cmdIndexElemValue.put(Integer.valueOf(i), new HashMap<String, String>());
                                }
                                cmdIndexElemValue.get(i).put(elem, (String) param);
                            }
                            times++;
                        }
                        if (times == replaceTimes) {
                            break;
                        }
                    }
                }
            }
        }
        cmdList = cmdList.stream().filter(cmd -> !cmd.contains("{")).distinct().collect(Collectors.toList());
        for (String cmd : cmdList) {
            int index = cmdList.indexOf(cmd);
            cmdMap.put(cmd, cmdIndexElemValue.get(index));
        }
        return cmdMap;
    }

    /**
     * private helper
     * make the map contained in the input JSON{@link String} {@code jsonString} into a {@link HashMap}
     * @param jsonObj
     * @return
     */
    @SuppressWarnings("unchecked")
    private HashMap<String, String> JSONStringToHashMap(String jsonString) {
        LinkedHashMap<String, String> result = new LinkedHashMap<>();
        if (null != jsonString) {
            result = JSON.parseObject(jsonString, LinkedHashMap.class, Feature.OrderedField);
        }
        return result;
    }

    public Map<String, List<Object>> xml2ParamMap(Object queryResult, Map<String, String> elements, String prefix) {
        String xmlStr = (String) queryResult;
        InputSource is = new InputSource();
        DocumentBuilder db;
        is.setCharacterStream(new StringReader(xmlStr));
        Document doc;
        Map<String, List<Object>> map = new HashMap<String, List<Object>>();
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            db = dbf.newDocumentBuilder();
            doc = db.parse(is);
            NodeList nl = doc.getChildNodes();
            List<Element> list = removePrefix(nl, prefix);

            for (Element element : list) {
                if (elements != null) {
                    elements.keySet().forEach(keyName -> {
                        Object value = getElementValue(element, elements.get(keyName));
                        if (!map.containsKey(keyName)) {
                            map.put(keyName, new ArrayList<Object>());
                        }
                        map.get(keyName).add(value);
                    });
                }
            }
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        return map;
    }

    public String paramsToResult(String resultStr, Map<String, String> elemValueMap) {
        if (elemValueMap != null && !elemValueMap.keySet().isEmpty()) {
            for (String elem : elemValueMap.keySet()) {
                String elemValue = elemValueMap.get(elem);
                if (!StringUtils.isNotBlank(elemValue)) {
                    continue;
                }
                String[] elemKeys = elem.split("###");
                String parentElem = elemKeys[0];
                String elemAdd = elemKeys[1];
                resultStr = resultStr.replace("<" + parentElem + ">",
                    "<" + parentElem + ">" + "<" + elemAdd + ">" + elemValue + "</" + elemAdd + ">");

            }
        }
        return resultStr;
    }

    private String getElementValue(Element element, String key) {
        List<Element> list = new ArrayList<>();
        list.add(element);
        String[] strs = key.split("###");
        for (int i = 0; i < strs.length - 1; i++) {
            String[] filters = strs[i].split("@@@");
            List<Element> curList = getChildNodesByName(list, filters[0]);
            if (filters.length > 1) {
                curList = filterByAttribute(curList, filters[1], filters[2]);
            }
            list = curList;
        }
        if (list.size() != 1) {
            logger.error("extract target string error: list " + list.toString());
            return null;
        }
        String attr = strs[strs.length - 1];
        if (attr.equals(":::text")) {
            return list.get(0).getTextContent();
        } else {
            return list.get(0).getAttribute(strs[strs.length - 1]);
        }
    }

    private List<Element> removePrefix(NodeList nodeList, String prefix) {
        List<Element> list = new ArrayList<>();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                list.add((Element) node);
            }
        }
        if (prefix.length() == 0) {
            return list;
        }
        String[] filters = prefix.split("###");
        for (int i = 0; i < filters.length; i++) {
            String[] strs = filters[i].split("@@@");
            List<Element> curList = null;
            if (!strs[0].equals("***")) {
                curList = getNodesByName(list, strs[0]);
            } else {
                curList = list;
            }
            if (strs.length > 1) {
                curList = filterByAttribute(curList, strs[1], strs[2]);
            }
            list = curList;
            if (i != filters.length - 1) {
                list = getChildNodes(curList);
            }
        }
        return list;
    }

    private List<Element> getChildNodes(List<Element> list) {
        List<Element> result = new ArrayList<>();
        for (Element node : list) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                    result.add((Element) children.item(i));
                }
            }
        }
        return result;
    }

    private List<Element> getChildNodesByName(List<Element> list, String name) {
        List<Element> result = new ArrayList<>();
        for (Element node : list) {
            NodeList children = node.getChildNodes();
            result.addAll(getNodesByName(children, name));
        }
        return result;
    }

    private List<Element> getNodesByName(NodeList nl, String name) {
        List<Element> result = new ArrayList<>();
        for (int i = 0; i < nl.getLength(); i++) {
            if (nl.item(i).getNodeName().equals(name) && nl.item(i).getNodeType() == Node.ELEMENT_NODE) {
                result.add((Element) nl.item(i));
            }
        }
        return result;
    }

    private List<Element> getNodesByName(List<Element> list, String name) {
        List<Element> result = new ArrayList<>();
        for (Element n : list) {
            if (n.getNodeName().equals(name)) {
                result.add(n);
            }
        }
        return result;
    }

    private List<Element> filterByAttribute(List<Element> list, String attrName, String attrValue) {
        List<Element> result = new ArrayList<>();
        List<String> attrValList = Arrays.asList(attrValue.split("!!!"));
        for (Element ele : list) {
            if (ele.hasAttribute(attrName) && attrValList.contains(ele.getAttribute(attrName))) {
                result.add(ele);
            }
        }
        return result;
    }
}
